package viewer;

import java.awt.*;

import model.RoffVector3D;

public class RoffPhong { 

    private RoffVector3D lightDirection;
    private RoffVector3D worldLight;

    private float objOutsideRed;
    private float objOutsideGreen;
    private float objOutsideBlue;

    private float objInsideRed;
    private float objInsideGreen;
    private float objInsideBlue;

    private float ambientRed;
    private float ambientGreen;
    private float ambientBlue;

    private float lightRed;
    private float lightGreen;
    private float lightBlue;

    private float highlightRed;
    private float highlightGreen;
    private float highlightBlue;

    private float ambientFactor;
    private float diffuseFactor;
    private float specularFactor;

    private float shininess;

    private float transparency;

    public RoffPhong() {

        lightDirection = new RoffVector3D(1, 1, -1);
        lightDirection.normalize();
        worldLight     = new RoffVector3D(1, 1, -1);
        worldLight.normalize();

        objOutsideRed   = 0.2f;
        objOutsideGreen = 0.4f;
        objOutsideBlue  = 1.0f;

        objInsideRed   = 1.0f;
        objInsideGreen = 0.2f;
        objInsideBlue  = 0.2f;

        ambientRed   = .3f;
        ambientGreen = .3f;
        ambientBlue  = .3f;

        lightRed   = 1.0f;
        lightGreen = 1.0f;
        lightBlue  = 1.0f;

        highlightRed   = 1.0f;
        highlightGreen = 1.0f;
        highlightBlue  = 1.0f;

        ambientFactor  = 1.0f;
        diffuseFactor  = 1.0f;
        specularFactor = 1.0f;

        shininess = 15.0f;

        transparency = 1.0f;
    }

    public RoffPhong(RoffPhong oldPhong) {

        lightDirection = new RoffVector3D(oldPhong.lightDirection.x,
                                        oldPhong.lightDirection.y,
                                        oldPhong.lightDirection.z);
        worldLight     = new RoffVector3D(oldPhong.worldLight.x,
                                        oldPhong.worldLight.y,
                                        oldPhong.worldLight.z);

        objOutsideRed   = oldPhong.objOutsideRed;
        objOutsideGreen = oldPhong.objOutsideGreen;
        objOutsideBlue  = oldPhong.objOutsideBlue;

        objInsideRed   = oldPhong.objInsideRed;
        objInsideGreen = oldPhong.objInsideGreen;
        objInsideBlue  = oldPhong.objInsideBlue;

        ambientRed   = oldPhong.ambientRed;
        ambientGreen = oldPhong.ambientGreen;
        ambientBlue  = oldPhong.ambientBlue;

        lightRed   = oldPhong.lightRed;
        lightGreen = oldPhong.lightGreen;
        lightBlue  = oldPhong.lightBlue;

        highlightRed   = oldPhong.highlightRed;
        highlightGreen = oldPhong.highlightGreen;
        highlightBlue  = oldPhong.highlightBlue;

        ambientFactor  = oldPhong.ambientFactor;
        diffuseFactor  = oldPhong.diffuseFactor;
        specularFactor = oldPhong.specularFactor;

        shininess = oldPhong.shininess;

        transparency = oldPhong.transparency;
    }

    /*
     * PRE: originalNormal.getLength() == 1
     *      AND
     *      viewDirection.getLength() == 1
     *      AND
     *      setWorldLight() has been called to set a usable value.
     */
    public Color illuminate(RoffVector3D originalNormal, RoffVector3D viewDirection) {

	RoffVector3D normal = new RoffVector3D(originalNormal.x, originalNormal.y, originalNormal.z);
        float objDiffuseRed   = objOutsideRed;
        float objDiffuseGreen = objOutsideGreen;
        float objDiffuseBlue  = objOutsideBlue;

        if (normal.dot(viewDirection) < 0.0) {
             normal = normal.scalarMultiply(-1.0);
             objDiffuseRed   = objInsideRed;
             objDiffuseGreen = objInsideGreen;
             objDiffuseBlue  = objInsideBlue;
	}

	double diffFactor = normal.dot(worldLight);
	final RoffVector3D refVector = (normal.scalarMultiply(2 * diffFactor)).sub(worldLight);
	refVector.normalize();
	double specFactor = refVector.dot(viewDirection);

        float redPart   = ambientRed * ambientFactor * objDiffuseRed; 
        float greenPart = ambientGreen * ambientFactor * objDiffuseGreen;
        float bluePart  = ambientBlue * ambientFactor * objDiffuseBlue;

        if (diffFactor >= 0.0) {
            if (specFactor >= 0.0) {
                float specMultiplier = (float)(specularFactor * Math.pow(specFactor, shininess));
                redPart   = redPart + specMultiplier * highlightRed * lightRed;
                greenPart = greenPart + specMultiplier * highlightGreen * lightGreen;
                bluePart  = bluePart + specMultiplier * highlightBlue * lightBlue;
            }
            float diffMultiplier = (float)(diffuseFactor * diffFactor);
            redPart   = redPart + diffMultiplier * objDiffuseRed * lightRed;
            greenPart = greenPart + diffMultiplier * objDiffuseGreen * lightGreen;
            bluePart  = bluePart + diffMultiplier * objDiffuseBlue * lightBlue;
	}
    
	if (redPart > 1.0) {
            redPart = 1.0f;
	}
	if (greenPart > 1.0) {
            greenPart = 1.0f;
	}
	if (bluePart > 1.0) {
            bluePart = 1.0f;
	}

	return new Color(redPart, greenPart, bluePart, transparency);
    }

    public void setOutsideColor(Color newColor) {
        float[] components = newColor.getRGBColorComponents(null);
        objOutsideRed   = components[0];
        objOutsideGreen = components[1];
        objOutsideBlue  = components[2];
    }

    public void setInsideColor(Color newColor) {
        float[] components = newColor.getRGBColorComponents(null);
        objInsideRed   = components[0];
        objInsideGreen = components[1];
        objInsideBlue  = components[2];
    }

    public void setAmbientColor(Color newColor) {
        float[] components = newColor.getRGBColorComponents(null);
        ambientRed   = components[0];
        ambientGreen = components[1];
        ambientBlue  = components[2];
    }

    public void setDirectionalColor(Color newColor) {
        float[] components = newColor.getRGBColorComponents(null);
        lightRed   = components[0];
        lightGreen = components[1];
        lightBlue  = components[2];
    }

    public void setHighlightColor(Color newColor) {
        float[] components = newColor.getRGBColorComponents(null);
        highlightRed   = components[0];
        highlightGreen = components[1];
        highlightBlue  = components[2];
    }

    public void setLightDirection(RoffVector3D newDirection) {
        lightDirection = newDirection.scalarDivide(newDirection.getLength());
    }

    public void setWorldLight(RoffVector3D newDirection) {
        worldLight = newDirection.scalarDivide(newDirection.getLength());
    }

    public Color getOutsideColor() {
        return new Color(objOutsideRed, objOutsideGreen, objOutsideBlue);
    }

    public Color getInsideColor() {
        return new Color(objInsideRed, objInsideGreen, objInsideBlue);
    }

    public Color getAmbientColor() {
        return new Color(ambientRed, ambientGreen, ambientBlue);
    }

    public Color getDirectionalColor() {
        return new Color(lightRed, lightGreen, lightBlue);
    }

    public Color getHighlightColor() {
        return new Color(highlightRed, highlightGreen, highlightBlue);
    }

    public RoffVector3D getLightDirection() {
	return new RoffVector3D(lightDirection.x, lightDirection.y, lightDirection.z);
    }

    public RoffVector3D getWorldLight() {
	return new RoffVector3D(worldLight.x, worldLight.y, worldLight.z);
    }

    public double getShininess() {
        return (double)shininess;
    }

    public void setShininess(double newShininess) {
        shininess = (float)newShininess;
    }

    public double getTransparency() {
        return (double)transparency;
    }

    void setTransparency(double newTransparency) {
        transparency = (float)newTransparency;
    }

    public double getAmbientFactor() {
        return ambientFactor;
    }

    public void setAmbientFactor(double newFactor) { 
        ambientFactor = (float)newFactor;
    }

    public double getDiffuseFactor() {
        return diffuseFactor;
    }

    public void setDiffuseFactor(double newFactor) {
        diffuseFactor = (float)newFactor;
    }

    public double getSpecularFactor() {
        return specularFactor;
    }
 
    public void setSpecularFactor(double newFactor) {
        specularFactor = (float)newFactor;
    }
}